// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/gesture_event_queue.h"

#include "base/auto_reset.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
#include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
#include "ui/events/blink/blink_features.h"
#include "ui/events/blink/web_input_event_traits.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;

namespace content {

GestureEventQueue::GestureEventWithLatencyInfoAndAckState::
    GestureEventWithLatencyInfoAndAckState(
        const GestureEventWithLatencyInfo& event)
    : GestureEventWithLatencyInfo(event) {}

GestureEventQueue::Config::Config() {
}

GestureEventQueue::GestureEventQueue(
    GestureEventQueueClient* client,
    FlingControllerEventSenderClient* fling_event_sender_client,
    FlingControllerSchedulerClient* fling_scheduler_client,
    const Config& config)
    : client_(client),
      scrolling_in_progress_(false),
      debounce_interval_(config.debounce_interval),
      fling_controller_(fling_event_sender_client,
                        fling_scheduler_client,
                        config.fling_config) {
  DCHECK(client);
  DCHECK(fling_event_sender_client);
  DCHECK(fling_scheduler_client);
}

GestureEventQueue::~GestureEventQueue() { }

bool GestureEventQueue::DebounceOrForwardEvent(
    const GestureEventWithLatencyInfo& gesture_event) {
  // GFS and GFC should have been filtered in PassToFlingController.
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingStart);
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingCancel);
  if (!ShouldForwardForBounceReduction(gesture_event))
    return false;

  ForwardGestureEvent(gesture_event);
  return true;
}

bool GestureEventQueue::PassToFlingController(
    const GestureEventWithLatencyInfo& gesture_event) {
  return fling_controller_.ObserveAndMaybeConsumeGestureEvent(gesture_event);
}

void GestureEventQueue::QueueDeferredEvents(
    const GestureEventWithLatencyInfo& gesture_event) {
  deferred_gesture_queue_.push_back(gesture_event);
}

GestureEventQueue::GestureQueue GestureEventQueue::TakeDeferredEvents() {
  GestureQueue deferred_gesture_events;
  deferred_gesture_events.swap(deferred_gesture_queue_);
  return deferred_gesture_events;
}

void GestureEventQueue::StopFling() {
  fling_controller_.StopFling();
}

gfx::Vector2dF GestureEventQueue::CurrentFlingVelocity() const {
  return fling_controller_.CurrentFlingVelocity();
}

bool GestureEventQueue::FlingInProgressForTest() const {
  return fling_controller_.fling_in_progress();
}

bool GestureEventQueue::ShouldForwardForBounceReduction(
    const GestureEventWithLatencyInfo& gesture_event) {
  if (debounce_interval_ <= base::TimeDelta())
    return true;

  // Don't debounce any gesture events while a fling is in progress on the
  // browser side. A GSE event in this case ends fling progress and it shouldn't
  // get cancelled by its next GSB event.
  if (fling_controller_.fling_in_progress())
    return true;

  switch (gesture_event.event.GetType()) {
    case WebInputEvent::Type::kGestureScrollUpdate:
      // This will restart the timer if it is already running.
      debounce_deferring_timer_.Start(
          FROM_HERE, debounce_interval_, this,
          &GestureEventQueue::SendScrollEndingEventsNow);
      scrolling_in_progress_ = true;
      debouncing_deferral_queue_.clear();
      return true;

    case WebInputEvent::Type::kGesturePinchBegin:
    case WebInputEvent::Type::kGesturePinchEnd:
    case WebInputEvent::Type::kGesturePinchUpdate:
      return true;
    case WebInputEvent::Type::kGestureScrollBegin:
      // GSB event should not get filtered by the debouncing_deferral_queue_
      // when its previous GSE event is forwarded to the renderer.
      if (!scroll_end_filtered_by_deboucing_deferral_queue_) {
        return true;
      } else {
        debouncing_deferral_queue_.push_back(gesture_event);
        return false;
      }
    case WebInputEvent::Type::kGestureScrollEnd:
      scroll_end_filtered_by_deboucing_deferral_queue_ = false;
      // GSEs generated by the fling controller should not get pushed to the
      // debouncing_deferral_queue_.
      if (gesture_event.event.data.scroll_end.generated_by_fling_controller) {
        scrolling_in_progress_ = false;
        return true;
      }
      if (scrolling_in_progress_) {
        debouncing_deferral_queue_.push_back(gesture_event);
        scroll_end_filtered_by_deboucing_deferral_queue_ = true;
        return false;
      }
      return true;
    default:
      if (scrolling_in_progress_) {
        debouncing_deferral_queue_.push_back(gesture_event);
        return false;
      }
      return true;
  }
}

void GestureEventQueue::ForwardGestureEvent(
    const GestureEventWithLatencyInfo& gesture_event) {
  // GFS and GFC should have been filtered in PassToFlingController to get
  // handled by fling controller.
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingStart);
  DCHECK_NE(gesture_event.event.GetType(),
            WebInputEvent::Type::kGestureFlingCancel);
  sent_events_awaiting_ack_.push_back(gesture_event);
  client_->SendGestureEventImmediately(gesture_event);
}

void GestureEventQueue::ProcessGestureAck(
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result,
    WebInputEvent::Type type,
    const ui::LatencyInfo& latency) {
  TRACE_EVENT0("input", "GestureEventQueue::ProcessGestureAck");

  if (sent_events_awaiting_ack_.empty()) {
    DLOG(ERROR) << "Received unexpected ACK for event type " << type;
    return;
  }

  // ACKs could come back out of order. We want to cache them to restore the
  // original order.
  for (auto& outstanding_event : sent_events_awaiting_ack_) {
    if (outstanding_event.ack_state() !=
        blink::mojom::InputEventResultState::kUnknown)
      continue;
    if (outstanding_event.event.GetType() == type) {
      outstanding_event.latency.AddNewLatencyFrom(latency);
      outstanding_event.set_ack_info(ack_source, ack_result);
      break;
    }
  }

  AckCompletedEvents();
}

void GestureEventQueue::AckCompletedEvents() {
  // Don't allow re-entrancy into this method otherwise
  // the ordering of acks won't be preserved.
  if (processing_acks_)
    return;
  base::AutoReset<bool> process_acks(&processing_acks_, true);
  while (!sent_events_awaiting_ack_.empty()) {
    auto iter = sent_events_awaiting_ack_.begin();
    if (iter->ack_state() == blink::mojom::InputEventResultState::kUnknown)
      break;
    GestureEventWithLatencyInfoAndAckState event = *iter;
    sent_events_awaiting_ack_.erase(iter);
    AckGestureEventToClient(event, event.ack_source(), event.ack_state());
  }
}

void GestureEventQueue::AckGestureEventToClient(
    const GestureEventWithLatencyInfo& event_with_latency,
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result) {
  client_->OnGestureEventAck(event_with_latency, ack_source, ack_result);
}

TouchpadTapSuppressionController*
    GestureEventQueue::GetTouchpadTapSuppressionController() {
  return fling_controller_.GetTouchpadTapSuppressionController();
}

void GestureEventQueue::SendScrollEndingEventsNow() {
  scrolling_in_progress_ = false;
  if (debouncing_deferral_queue_.empty())
    return;
  GestureQueue debouncing_deferral_queue;
  debouncing_deferral_queue.swap(debouncing_deferral_queue_);
  for (GestureQueue::const_iterator it = debouncing_deferral_queue.begin();
       it != debouncing_deferral_queue.end(); it++) {
    if (!fling_controller_.ObserveAndMaybeConsumeGestureEvent(*it)) {
      if (it->event.GetType() == WebInputEvent::Type::kGestureScrollEnd)
        scroll_end_filtered_by_deboucing_deferral_queue_ = false;
      ForwardGestureEvent(*it);
    }
  }
}

void GestureEventQueue::OnWheelEventAck(
    const MouseWheelEventWithLatencyInfo& event,
    blink::mojom::InputEventResultSource ack_source,
    blink::mojom::InputEventResultState ack_result) {
  fling_controller_.OnWheelEventAck(event, ack_source, ack_result);
}

}  // namespace content
